/*-
 * SMSStatusReport.java
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 98 $
 * $LastChangedDate: 2007-02-20 15:18:26 +0000 (Tue, 20 Feb 2007) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/pdu/SMSStatusReport.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.sms.pdu;

import java.util.EnumSet;

import com.googlecode.attention.util.IntArrayBuilder;
import com.googlecode.attention.util.KeepingReference;

import static com.googlecode.attention.sms.pdu.PDUElement.*;

/** * SMS-STATUS-REPORT. * <BR> * <BR>Basic elements of the SMS-STATUS-REPORT type: * <BR> * <BR> * <TABLE BORDER="1"> * <TR><TD>Abbr.</TD><TD>Reference</TD><TD>P1)</TD><TD>R2)</TD><TD>Description</TD></TR> * <TR><TD>TP-MTI</TD><TD>TP-Message-Type-Indicator</TD><TD>M</TD><TD>2b</TD><TD>Parameter describing the message type</TD></TR> * <TR><TD>TP-UDHI</TD><TD>TP-User-Data-Header-Indication</TD><TD>O</TD><TD>b</TD><TD>Parameter indicating that the TP-UD field contains a Header</TD></TR> * <TR><TD>TP-MMS</TD><TD>TP-More-Messages-to-Send</TD><TD>M</TD><TD>b</TD><TD>Parameter indicating whether or not there are more messages to send</TD></TR> * <TR><TD>TP-SRQ</TD><TD>TP-Status-Report-Qualifier</TD><TD>M</TD><TD>b</TD><TD>Parameter indicating whether the previously submitted TPDU was an SMS-SUBMIT or an SMS-COMMAND</TD></TR> * <TR><TD>TP-MR</TD><TD>TP-Message-Reference 3)</TD><TD>M</TD><TD>I</TD><TD>Parameter identifying the previously submitted SMS-SUBMIT or SMS-COMMAND</TD></TR> * <TR><TD>TP-RA</TD><TD>TP-Recipient-Address</TD><TD>M</TD><TD>2-12o</TD><TD>Address of the recipient of the previously submitted mobile originated short message</TD></TR> * <TR><TD>TP-SCTS</TD><TD>TP-Service-Centre-Time-Stamp</TD><TD>M</TD><TD>7o</TD><TD>Parameter identifying time when the SC received the previously sent SMS-SUBMIT</TD></TR> * <TR><TD>TP-DT</TD><TD>TP-Discharge-Time</TD><TD>M</TD><TD>7o</TD><TD>Parameter identifying the time associated with a particular TP-ST outcome</TD></TR> * <TR><TD>TP-ST</TD><TD>TP-Status</TD><TD>M</TD><TD>o</TD><TD>Parameter identifying the status of the previously sent mobile originated short message</TD></TR> * <TR><TD>TP-PI</TD><TD>TP-Parameter-Indicator</TD><TD>O 4)</TD><TD>o</TD><TD>Parameter indicating the presence of any of the optional parameters which follow</TD></TR> * <TR><TD>TP-PID</TD><TD>TP-Protocol-Identifier</TD><TD>O</TD><TD>o</TD><TD>See PID. TP-PID of original SMSSUBMIT</TD></TR> * <TR><TD>TP-DCS</TD><TD>TP-Data-Coding-Scheme</TD><TD>O</TD><TD>o</TD><TD>See DCS</TD></TR> * <TR><TD>TP-UDL</TD><TD>TP-User-Data-Length</TD><TD>O</TD><TD>o</TD><TD>See UD.getUDL()</TD></TR> * <TR><TD>TP-UD</TD><TD>TP-User-Data</TD><TD>O</TD><TD>5)</TD><TD>See UD</TD></TR> * </TABLE> * <BR> * <BR>1) Provision: Mandatory (M) or Optional (O). * <BR>2) Representation: Integer (I), bit (b), 2 bits (2b), Octet (o), * 7 octets (7o), 2-12 octets (2-12o). * <BR>3) Where the SMS-STATUS-REPORT is the result of an SMS-COMMAND and * the TP-Command-Type was an Enquiry, the TP-MR returned in the * SMS-STATUS-REPORT shall be the TP-MN which was sent in the SMS-COMMAND * (i.e. the TP-MR of the previously submitted SM to which the Enquiry refers). * <BR>4) Mandatory if any of the optional parameters following TP-PI is * present, otherwise optional. * <BR>5) TP-UD contains information related to a SMS-DELIVER; can contain * information transported in the TP-UD of SMS-DELIVER-REPORT, and information * inserted by the SMSC. The length of the TP-UD field is limited and might * not be long enough to fit information both from the original receiving * terminal (as included into the SMSDELIVER-REPORT) and information added * by the SMSC. In these cases the former information has higher priority, * and the latter shall be truncated. * <BR> * <BR>NOTE: Any unused bits shall be set to zero by the sending entity and * shall be ignored by the receiving entity. The maximum guaranteed length of * TP-UD is 131 octets. In order to achieve the maximum stated above * (143 octets), the TP-RA field must have a length of 2 octets and TP-PID * and TP-DCS must not be present. *  * @author Stein Gunnar Bakkeby */
public class SMSStatusReport extends TPDU {
  /* Field values */
  private int fo = 0;
  private int mr = 0;
  private Address ra = null;
  private SCTS scts = null;
  private SCTS dt = null;
  private int st  = 0;
  private int pi  = 0;
  private int pid = 0;
  private int dcs = 0;
  private int extensionOctet = 0;

  private EnumSet<PDUElement> pduElementSet = EnumSet.of(TP_FO, TP_MTI,
      TP_UDHI, TP_MMS, TP_SRQ, TP_MR, TP_RA, TP_TOA, TP_NPI, TP_TON, TP_SCTS,
      TP_DT, TP_ST, TP_PI, TP_PID, TP_DCS, TP_UDL, TP_UD, TP_UDH);
  
  /**
   * Constructor, creates an empty SMS-STATUS-REPORT.
   */
  public SMSStatusReport() {
    super(new int[1], TPDU.SMS_STATUS_REPORT);
  }
  
  /**
   * Constructor, creates the SMS-STATUS-REPORT based upon the given PDU.
   * 
   * @param pdu The raw data
   */
  public SMSStatusReport(final String pdu) {
    super(pdu, TPDU.SMS_STATUS_REPORT);
  }
  
  /**
   * Constructor, creates the SMS-STATUS-REPORT based upon the given PDU.
   * 
   * @param pdu The raw data
   */
  public SMSStatusReport(final int[] pdu) {
    super(pdu, TPDU.SMS_STATUS_REPORT);
  }
  
  /**
   * Constructor, creates the SMS-STATUS-REPORT.
   * 
   * @param fo The first octet
   * @param mr The Message-Reference
   * @param ra The Recipient-Address
   * @param scts The Service-Centre-Time-Stamp
   * @param dt The Discharge-Time
   * @param st The Status
   * @param pi The Parameter-Identifier
   * @param pid The Protocol-Identifier
   * @param dcs The Data-Coding-Scheme
   * @param ud The User-Data
   */
  public SMSStatusReport(final FO fo,
                         final MR mr,
                         final Address ra,
                         final SCTS scts,
                         final SCTS dt,
                         final ST st,
                         final PI pi,
                         final PID pid,
                         final DCS dcs,
                         final UD ud) {
    super(new int[1], TPDU.SMS_STATUS_REPORT);
    this.fo   = fo.getValue();
    this.mr   = mr.getValue();
    this.ra   = ra;
    this.scts = scts;
    this.dt   = dt;
    this.st   = st.getValue();
    this.pi   = pi.getValue();
    this.pid  = pid.getValue();
    this.dcs  = dcs.getValue();
    this.ud   = ud;
  }
  
  /**
   * Constructor, creates the SMS-STATUS-REPORT using integer values.
   * <BR>Note that this constructor does not check the boundaries of
   * the integer values given, thus the constructor that takes actual
   * FO, MR, etc. objects is recommended.
   * 
   * @param fo The first octet
   * @param mr The Message-Reference
   * @param ra The Recipient-Address
   * @param scts The Service-Centre-Time-Stamp
   * @param dt The Discharge-Time
   * @param st The Status
   * @param pi The Parameter-Identifier
   * @param pid The Protocol-Identifier
   * @param dcs The Data-Coding-Scheme
   * @param ud The User-Data
   */
  public SMSStatusReport(final int fo,
                         final int mr,
                         final Address ra,
                         final SCTS scts,
                         final SCTS dt,
                         final int st,
                         final int pi,
                         final int pid,
                         final int dcs,
                         final UD ud) {
    super(new int[1], TPDU.SMS_STATUS_REPORT);
    this.fo   = fo;
    this.mr   = mr;
    this.ra   = ra;
    this.scts = scts;
    this.dt   = dt;
    this.st   = st;
    this.pi   = pi;
    this.pid  = pid;
    this.dcs  = dcs;
    this.ud   = ud;
  }
  
  /**
   * Constructor, creates the SMS-STATUS-REPORT without the optional
   * PI, PID, DCS, and UD.
   * 
   * @param fo The first octet
   * @param mr The Message-Reference
   * @param ra The Recipient-Address
   * @param scts The Service-Centre-Time-Stamp
   * @param dt The Discharge-Time
   * @param st The Status
   */
  public SMSStatusReport(final FO fo,
                         final MR mr,
                         final Address ra,
                         final SCTS scts,
                         final SCTS dt,
                         final ST st) {
    super(new int[1], TPDU.SMS_STATUS_REPORT);
    this.fo   = fo.getValue();
    this.mr   = mr.getValue();
    this.ra   = ra;
    this.scts = scts;
    this.dt   = dt;
    this.st   = st.getValue();
  }
  
  /**
   * Constructor, creates the SMS-STATUS-REPORT without the optional
   * PI, PID, DCS, and UD using integer values.
   * <BR>Note that this constructor does not check the boundaries of
   * the integer values given, thus the constructor that takes actual
   * FO, MR, etc. objects is recommended.
   * 
   * @param fo The first octet
   * @param mr The Message-Reference
   * @param ra The Recipient-Address
   * @param scts The Service-Centre-Time-Stamp
   * @param dt The Discharge-Time
   * @param st The Status
   */
  public SMSStatusReport(final int fo,
                         final int mr,
                         final Address ra,
                         final SCTS scts,
                         final SCTS dt,
                         final int st) {
    super(new int[1], TPDU.SMS_STATUS_REPORT);
    this.fo   = fo;
    this.mr   = mr;
    this.ra   = ra;
    this.scts = scts;
    this.dt   = dt;
    this.st   = st;
  }
  
  /**
   * This will decode the SMS-STATUS-REPORT.
   */
  public void decode() {
    resetPDUPointer();
    fo   = extractOctet();
    mr   = extractOctet();
    final int ral = extractOctet(); // oa length
    final int toa = extractOctet(); 
    final int numberOfAddressOctets = ((ral & 1) != 0 ? ral + 1 : ral) / 2;
    ra   = new Address(ral, new TOA(toa), extractOctets(numberOfAddressOctets));
    scts = new SCTS(extractOctets(7));
    dt   = new SCTS(extractOctets(7));
    st   = extractOctet();
    if (hasMoreOctets() && pdu.intAt(pduPointer) != 0xFF) {
      pi = extractOctet();
      final PI thisPI = new PI(pi);
      if (thisPI.isExtensionOctetIndicated()) {
        extensionOctet = extractOctet(); // remove and ignore
      }
      pid  = thisPI.isPIDIndicated() ? extractOctet() : 0;
      dcs  = thisPI.isDCSIndicated() ? extractOctet() : 0;
      final int udl = thisPI.isUDLIndicated() ? extractOctet() : 0;
      ud = new UD(udl > 0 ? extractOctets(udl) : new int[0],
                  udl,
                  UDHI.parse(fo),
                  new DCS(dcs));
    }
  }
  
  /**
   * This will encode the SMS-STATUS-REPORT.
   */
  public void encode() {
    /* Avoid NullPointerException */
    if (scts == null) scts = new SCTS();
    if (ra   == null) ra   = new Address();
    if (dt   == null) dt   = new SCTS();
    
    final IntArrayBuilder output = new IntArrayBuilder(INIT_SIZE);
    output.append(fo);
    output.append(mr);
    output.append(ra.getAddressField());
    output.append(scts.getVP());
    output.append(dt.getVP());
    output.append(st);
    
    if (pi != 0) {
      output.append(this.pi);
      final PI pi = new PI(this.pi);
      if (pi.isExtensionOctetIndicated()) {
        output.append(extensionOctet);
      }
      if (pi.isPIDIndicated()) output.append(pid);
      if (pi.isDCSIndicated()) output.append(dcs);
      if (pi.isUDLIndicated()) {
        output.append(ud.getUDL());
        output.append(ud);
      }
    }
    if (rest != null && rest.length != 0) {
      output.append(rest);
    }
    super.pdu = output;
  }
  
  /**
   * This will return true if the PDU contains the given type.
   *
   * @param type PDUElement values
   * @see PDUElement
   * @return True if given type is present, false otherwise
   */
  public boolean contains(final PDUElement type) {
    return pduElementSet.contains(type);
  }
  
  /**
   * This will return an element of a given type.
   * 
   * @param type See static field values
   * @return The element in this TPDU
   */
  public int get(final PDUElement type) {
    switch (type) {
    case TP_FO:   return fo;
    case TP_MTI:  return fo & TP_MTI.value;
    case TP_UDHI: return fo & TP_UDHI.value;
    case TP_MMS:  return fo & TP_MMS.value;
    case TP_SRQ:  return fo & TP_SRQ.value;
    case TP_MR:   return mr;
    case TP_ST:   return st;
    case TP_PI:   return pi;
    case TP_PID:  return pid;
    case TP_DCS:  return dcs;
    case TP_TOA: return (ra == null ? 0 : ra.getTOA().getValue());
    case TP_TON: return (ra == null ? 0 : ra.getTOA().getTypeOfNumber());
    case TP_NPI: return (ra == null ? 0 : ra.getTOA().getNumPlanIdent());
    case TP_UDL:  return ud.getUDL();
    
    default:
      return -1;
    }
  }
  
  /**
   * This will return the discharge time.
   * 
   * @return The DT of this status report.
   */
  public SCTS getDischargeTime() {
    return dt;
  }
  
  /**
   * This will return the recipient address.
   * 
   * @return The recipient address of this status report.
   */
  public Address getRecipientAddress() {
    return ra;
  }
  
  /**
   * This will return the service centre time stamp.
   * 
   * @return The SCTS of this status report.
   */
  public SCTS getServiceCentreTimeStamp() {
    return scts;
  }
  
  /**
   * This will set an element of a given type.
   * 
   * @param type See static field values
   * @param value The value of the element to set
   * @return True if PDU contains the given type, false otherwise
   */
  public boolean set(final PDUElement type, final int value) {
    switch (type) {
    case TP_FO:
      fo = value;
      break;
      
    case TP_MR:
      mr = value;
      break;
      
    case TP_ST:
      st = value;
      break;
      
    case TP_PI:
      pi = value;
      break;
      
    case TP_PID:
      pid = value;
      break;
      
    case TP_DCS:
      dcs = value;
      break;
      
    case TP_MTI:
    case TP_UDHI:
    case TP_MMS:
    case TP_SRQ:
      final FO f = new FO(fo);
      f.set(type, value);
      fo = f.getValue();
      break;
    
    default:
      return false;
    }
    return true;
  }
  
  /**
   * This will set an element of a given type.
   * 
   * @param type See static field values
   * @param value The value of the element to set
   * @return True if PDU contains the given type, false otherwise
   */
  @KeepingReference(ref="value")
  public boolean set(final PDUElement type, final int[] value) {
    switch (type) {
    case TP_UD:
      if (ud == null) ud = new UD();
      ud.setUD(value);
      break;
      
    case TP_UDH:
      ud.setUDH(new UDH(value));
      break;
      
    case TP_DT:
      dt = new SCTS(value);
      break;
      
    case TP_SCTS:
      scts = new SCTS(value);
      break;
      
    case TP_RA:
      ra = new Address(value);
      break;
    
    default:
      return false;
    }
    return true;
  }
  
  /**
   * This will set the recipient address.
   * 
   * @param address The RA of this status report.
   */
  @KeepingReference
  public void setRecipientAddress(final Address address) {
    this.ra = address;
  }
  
  /**
   * This will set the service centre time stamp.
   * 
   * @param timestamp The SCTS of this status report.
   */
  @KeepingReference
  public void setServiceCentreTimeStamp(final SCTS timestamp) {
    this.scts = timestamp;
  }
  
  /**
   * This will set the discharge time.
   * 
   * @param dischargeTime The TP-DT to set.
   */
  @KeepingReference
  public void setDischargeTime(final SCTS dischargeTime) {
    this.dt = dischargeTime;
  }
  
  /**
   * This is an SMS report and therefore does not have an originators address
   * or a destination address. Because of this the method will return null.
   * 
   * @return null
   */
  @Override
  public Address getAddress() {
    return getRecipientAddress();
  }
  
  /**
   * This will set the recipient address.
   * 
   * @param address TP-RA
   */
  @Override
  public void setAddress(final Address address) {
    setRecipientAddress(address);
  }
  
  /**
   * @return [FO:&lt;fo&gt;][MR:&lt;mr&gt;][RA:&lt;address&gt;][SCTS:&lt;scts&gt;][DT:&lt;scts&gt;][ST:&lt;st&gt;][PID:&lt;pid&gt;][DCS:&lt;dcs&gt;][UD:&lt;ud&gt;]
   */
  public String toString() {
    this.decode();
    final StringBuilder sb = new StringBuilder(256);
    sb.append("[FO:");
    sb.append(fo);
    sb.append("][MR:");
    sb.append(mr);
    sb.append("][RA:");
    sb.append(ra);
    sb.append("][SCTS:"); 
    sb.append(scts);
    sb.append("][DT:");
    sb.append(dt);
    sb.append("][ST:");
    sb.append(st);
    sb.append("][PI:");
    sb.append(pi);
    final PI pi = new PI(this.pi);
    if (pi.isExtensionOctetIndicated()) {
      sb.append("][EXT:");
      sb.append(extensionOctet);
    }
    if (pi.isPIDIndicated()) {
      sb.append("][PID:");
      sb.append(pid);
    }
    if (pi.isDCSIndicated()) {
      sb.append("][DCS:");
      sb.append(dcs);
    }
    if (pi.isUDLIndicated()) {
      sb.append("][UD:");
      sb.append(ud);
    }
    sb.append(']');
    return sb.toString();
  }
}